#include "BSException/BSException.h"
#include "Encoding/Unicode.h"
#include "Encoding/UTF8.h"
#include "Encoding/GBK.h"
#include "Directory.h"

using namespace SystemTool;
using namespace SystemTool::IO;
using namespace SystemTool::Encoding;

// Create new directory recursively
bool Directory::Create(std::string strDirPath, bool bIsRecursive)
{
	if (strDirPath.empty())
	{
		return false;
	}

	try
	{
		try
		{
			boost::filesystem::path CurDirPath(strDirPath);

			boost::system::error_code ErrorCode;

			if (bIsRecursive)
			{
				if (!boost::filesystem::create_directories(CurDirPath, ErrorCode))
				{
					if (ErrorCode.value() == 0)
					{
						return true;
					}

					throw BSException() << ErrorCode.value() << ErrorCode.message();
				}
			}

			if (!boost::filesystem::create_directory(CurDirPath, ErrorCode))
			{
				if (ErrorCode.value() == 0)
				{
					return true;
				}

				throw BSException() << ErrorCode.value() << ErrorCode.message();
			}
		}
		catch (boost::filesystem::filesystem_error& e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException& e)
	{
		throw e;
	}
	
	return true;
}

// Copy the directory recursively
bool Directory::Copy(std::string strSrcDirPath, std::string strDestDirPath, bool bIsCover)
{
	if (strSrcDirPath.empty())
	{
		return false;
	}

	if (strDestDirPath.empty())
	{
		return false;
	}

	try
	{
		try
		{
			std::string strFinalSrcDirPath = AddEnding(strSrcDirPath);

			std::wstring strSourceDirPath = Unicode::GetGbkString(strFinalSrcDirPath, ENCODE_GBK);

			std::string strFinalDestDirPath = AddEnding(strDestDirPath);

			std::wstring strTargetDirPath = Unicode::GetGbkString(strFinalDestDirPath, ENCODE_GBK);

			boost::system::error_code ErrorCode;

			SearchIterator IterEnd;

			for (SearchIterator CurIter(strFinalSrcDirPath); CurIter != IterEnd; ++CurIter)
			{
				// Get source path of file or directory
				std::string strTempSrcPath = GetSearchItem(CurIter);

				std::string strSrcPath = "";

				TransformAsUnixPath(strTempSrcPath, strSrcPath);

				std::wstring strSourcePath = Unicode::GetGbkString(strSrcPath, ENCODE_GBK);

				// Get dest path of the file or directory
				size_t iPos = strSourcePath.find(strSourceDirPath.c_str(), 0, strSourceDirPath.length());

				if (iPos == std::string::npos)
				{
					continue;
				}

				std::wstring strRightPath = strSourcePath.substr(strSourceDirPath.length());

				std::wstring strTargetPath = strTargetDirPath + strRightPath;

				// Check wether the file's parent directory is existed
				std::string strSrcFilePath = GBK::GetString(strSourcePath,ENCODE_GBK);

				std::string strDestFilePath = GBK::GetString(strTargetPath,ENCODE_GBK);

				// If source path is directory, we just need to create dest path so that we can copy an empty src directory,too
				if (IsDirectory(strSrcFilePath))
				{
					if (!IsExisted(strDestFilePath))
					{
						// Create the parent directory of the new file
						if (!Create(strDestFilePath, true))
						{
							return false;
						}
					}

					continue;
				}

				std::string strParentDirPath = GetParentPath(strDestFilePath);

				if (!IsExisted(strParentDirPath))
				{
					// Create the parent directory of the new file
					if (!Create(strParentDirPath,true))
					{
						return false;
					}
				}

				// Copy the file here
				if (bIsCover)
				{
					if (!boost::filesystem::copy_file(strSrcFilePath,
						strDestFilePath,
						boost::filesystem::copy_option::overwrite_if_exists,
						ErrorCode))
					{
						if (ErrorCode.value() == 0)
						{
							return true;
						}

						throw BSException() << ErrorCode.value() << ErrorCode.message();
					}
				}
				else
				{
					if (!boost::filesystem::copy_file(strSrcFilePath,
						strDestFilePath,
						boost::filesystem::copy_option::none,
						ErrorCode))
					{
						if (ErrorCode.value() == 0 || ErrorCode.value() == 80)
						{
							return true;
						}

						throw BSException() << ErrorCode.value() << ErrorCode.message();
					}
				}
			}
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return true;
}

// Delete the directory recursively
bool Directory::Delete(std::string strDirPath, bool bIsRecursive)
{
	if (strDirPath.empty())
	{
		return false;
	}

	try
	{
		try
		{
			boost::filesystem::path CurDirPath(strDirPath);

			boost::system::error_code ErrorCode;

			if (bIsRecursive)
			{
				boost::filesystem::remove_all(CurDirPath, ErrorCode);

				if (ErrorCode.value() != 0)
				{
					throw BSException() << ErrorCode.value() << ErrorCode.message();
				}
			}
			else
			{
				if (!boost::filesystem::remove(CurDirPath, ErrorCode))
				{
					throw BSException() << ErrorCode.value() << ErrorCode.message();
				}
			}
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return true;
}

// Transform window's path to unix path with "/"
bool Directory::TransformAsUnixPath(std::string strDirPath, std::string& strFinalDirPath)
{
	if (strDirPath.empty())
	{
		return false;
	}

	strFinalDirPath = strDirPath;

	std::replace(strFinalDirPath.begin(), strFinalDirPath.end(), '\\', '/');

	return true;
}

// Add path ending with "/"
std::string Directory::AddEnding(std::string strDirPath)
{
	if (strDirPath.empty())
	{
		return "";
	}

	std::string strFinalDirPath = "";

	if (!TransformAsUnixPath(strDirPath, strFinalDirPath))
	{
		return strDirPath;
	}

	size_t iPos = strFinalDirPath.find_last_of('/');

	if (iPos == strFinalDirPath.length() - 1)
	{
		return strFinalDirPath;
	}

	strFinalDirPath += "/";

	return strFinalDirPath;
}

// Trim path ending without "/"
std::string Directory::TrimEnding(std::string strDirPath)
{
	if (strDirPath.empty())
	{
		return "";
	}

	std::string strFinalDirPath = "";

	if (!TransformAsUnixPath(strDirPath, strFinalDirPath))
	{
		return strDirPath;
	}

	size_t iPos = strFinalDirPath.find_last_of('/');

	if (iPos == -1)
	{
		return strFinalDirPath;
	}

	if (iPos != strFinalDirPath.length() - 1)
	{
		return strFinalDirPath;
	}

	strFinalDirPath = strFinalDirPath.substr(0, iPos);

	return strFinalDirPath;
}

// Is the directory existed or not
bool Directory::IsExisted(std::string strDirPath)
{
	if (strDirPath.empty())
	{
		return false;
	}

	try
	{
		try
		{
			boost::filesystem::path CurDirPath(strDirPath);

			boost::system::error_code ErrorCode;

			if (!boost::filesystem::exists(CurDirPath, ErrorCode))
			{
				return false;
			}
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return true;
}

// Is directoy None or not
bool Directory::IsEmpty(std::string strDirPath)
{
	if (strDirPath.empty())
	{
		return false;
	}

	try
	{
		try
		{
			boost::filesystem::path CurDirPath(strDirPath);

			boost::system::error_code ErrorCode;

			if (!boost::filesystem::is_empty(CurDirPath, ErrorCode))
			{
				return false;
			}
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return true;
}

// Is directory
bool Directory::IsDirectory(std::string strDirPath)
{
	if (strDirPath.empty())
	{
		return false;
	}

	try
	{
		try
		{
			boost::filesystem::path CurDirPath(strDirPath);

			boost::system::error_code ErrorCode;

			if (!boost::filesystem::is_directory(CurDirPath, ErrorCode))
			{
				return false;
			}
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return true;
}

// Set current directory of project
bool Directory::SetCurDirectory(std::string strDirPath)
{
	if (strDirPath.empty())
	{
		return false;
	}

	try
	{
		try
		{
			boost::filesystem::path CurDirPath(strDirPath);

			boost::filesystem::current_path(CurDirPath);
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return true;
}

// Get current directory of project
std::string Directory::GetCurDirectory()
{
	std::string strDirPath = "";

	try
	{
		try
		{
			boost::filesystem::path CurDirPath = boost::filesystem::current_path();

			strDirPath = AddEnding(CurDirPath.string());
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return strDirPath;
}

// Get exe's directory path with '/' ending
std::string Directory::GetExcutableDirectory()
{
	std::string strDirPath = "";

	try
	{
		try
		{
			boost::filesystem::path ExeDirPath = boost::filesystem::initial_path<boost::filesystem::path>();

			strDirPath = AddEnding(ExeDirPath.string());
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return strDirPath;
}

// Get last wite time of directory
std::time_t Directory::GetLastWriteTime(std::string strDirPath)
{
	if (strDirPath.empty())
	{
		return false;
	}

	time_t iLastWriteTime = 0;

	try
	{
		try
		{
			boost::filesystem::path CurDirPath(strDirPath);

			iLastWriteTime = boost::filesystem::last_write_time(CurDirPath);
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return iLastWriteTime;
}

// Set last wite time of directory
bool Directory::SetLastWriteTime(std::string strDirPath, std::time_t iNewTime)
{
	if (strDirPath.empty())
	{
		return false;
	}

	try
	{
		try
		{
			boost::filesystem::path CurDirPath(strDirPath);

			boost::filesystem::last_write_time(CurDirPath, iNewTime);
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return true;
}

// Get the space of the entire disk which containing this directory
bool Directory::GetDiskSpace(std::string strDirPath,
	unsigned long long& CurDiskCapacity,				// Total space of the disk
	unsigned long long& CurDiskFreeSpace,			// Free space of the disk
	unsigned long long& CurDiskAvailableSpace) // Avalible space of the disk
{
	if (strDirPath.empty())
	{
		return false;
	}

	try
	{
		try
		{
			boost::filesystem::path CurDirPath(strDirPath);

			boost::filesystem::space_info Info = boost::filesystem::space(CurDirPath);

			CurDiskCapacity = Info.capacity;

			CurDiskFreeSpace = Info.free;

			CurDiskAvailableSpace = Info.available;
		}
		catch (boost::filesystem::filesystem_error & e)
		{
			throw BSException() << e.code().value() << e.what();
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return true;
}

// Get rooth path of the directory
std::string Directory::GetRootPath(std::string strDirPath)
{
	if (strDirPath.empty())
	{
		return "";
	}

	boost::filesystem::path CurDirPath(strDirPath);

	boost::filesystem::path RootPath = CurDirPath.root_path();

	return RootPath.string();
}

// Get directory from the file path
std::string Directory::GetDirBy(std::string strFilePath)
{
	if (strFilePath.empty())
	{
		return "";
	}

	std::string strUnixFilePath = "";

	if (!TransformAsUnixPath(strFilePath, strUnixFilePath))
	{
		return strFilePath;
	}

	int iPos = strUnixFilePath.find_last_of('/');

	if (iPos == -1)
	{
		return "";
	}

	std::string strFinalPath = strUnixFilePath.substr(0, iPos);

	return strFinalPath;
}

// Get parent path of the directory
std::string Directory::GetParentPath(std::string strDirPath)
{
	if (strDirPath.empty())
	{
		return "";
	}

	boost::filesystem::path CurDirPath(strDirPath);

	boost::filesystem::path ParentPath = CurDirPath.parent_path();

	return ParentPath.string();
}

// Get search item
std::string Directory::GetSearchItem(SearchIterator& Iter)
{
	boost::filesystem::path CurSearchPath(*Iter);

	std::string strPath = CurSearchPath.string();

	return strPath;
}

// Get all files under a directory
bool Directory::GetAllFiles(std::string strDir, std::vector<std::string>& vFileTable)
{
	if (strDir.empty())
	{
		return false;
	}

	try
	{
		std::string strDirPath = "";

		TransformAsUnixPath(strDir, strDirPath);

		SearchIterator IterEnd;

		for (SearchIterator CurIter(strDirPath); CurIter != IterEnd; ++CurIter)
		{
			// Get source path of file or directory
			std::string strTempSrcPath = GetSearchItem(CurIter);

			if (!IsDirectory(strTempSrcPath))
			{
				vFileTable.push_back(strTempSrcPath);
			}
		}
	}
	catch (BSException & e)
	{
		throw e;
	}

	return true;
}
